<template>
  <div class="max-w-4xl mx-auto p-6">
    <h1 class="text-2xl font-bold mb-4">视频画面裁剪（浏览器端）</h1>

    <div class="bg-white shadow rounded p-4 mb-6">
      <p class="text-sm text-gray-600 mb-4">在浏览器端裁剪视频画面并导出裁剪后的视频。你可以输入裁剪框位置与大小进行导出。</p>

      <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
        <div>
          <label class="block text-sm font-medium text-gray-700 mb-2">选择视频文件</label>
          <input type="file" accept="video/*" @change="onFileChange" class="mb-3" />

          <div v-if="originalUrl">
            <h4 class="text-sm font-medium text-gray-700 mb-2">原始预览</h4>
            <video ref="originalVideo" :src="originalUrl" controls class="w-full bg-black rounded"></video>
            <p class="text-xs text-gray-500 mt-2">原始分辨率：{{ naturalResolution }} · 大小：{{ formatBytes(originalSize) }}</p>
          </div>
          <div v-else class="w-full h-48 bg-gray-50 rounded flex items-center justify-center text-gray-400">还没有选择视频</div>
        </div>

        <div>
          <h4 class="text-sm font-medium text-gray-700 mb-2">裁剪设置（以像素为单位）</h4>
          <div class="grid grid-cols-2 gap-2">
            <div>
              <label class="text-xs text-gray-600">x（左上）</label>
              <input type="number" v-model.number="cropX" class="w-full border rounded px-2 py-1" />
            </div>
            <div>
              <label class="text-xs text-gray-600">y（左上）</label>
              <input type="number" v-model.number="cropY" class="w-full border rounded px-2 py-1" />
            </div>
            <div>
              <label class="text-xs text-gray-600">宽度</label>
              <input type="number" v-model.number="cropW" class="w-full border rounded px-2 py-1" />
            </div>
            <div>
              <label class="text-xs text-gray-600">高度</label>
              <input type="number" v-model.number="cropH" class="w-full border rounded px-2 py-1" />
            </div>
          </div>

          <div class="flex space-x-3 mt-4">
            <button @click="startCrop" :disabled="!readyToStart || isProcessing" class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50">开始裁剪</button>
            <button @click="stopCrop" :disabled="!isProcessing" class="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700 disabled:opacity-50">停止</button>
            <button @click="downloadResult" :disabled="!resultBlob" class="px-4 py-2 bg-gray-700 text-white rounded hover:bg-gray-800 disabled:opacity-50">下载</button>
          </div>

          <div v-if="isProcessing" class="mt-4">
            <p class="text-sm text-gray-600">进度：{{ Math.floor(progress * 100) }}%</p>
            <div class="w-full bg-gray-200 rounded h-2 mt-2 overflow-hidden"><div :style="{width: (progress*100)+'%'}" class="h-2 bg-blue-600"></div></div>
          </div>

          <div v-if="resultBlob" class="mt-4">
            <p class="text-sm text-gray-600">完成：大小 {{ formatBytes(resultSize) }}</p>
            <video :src="resultUrl" controls class="w-full bg-black rounded mt-2"></video>
          </div>
        </div>
      </div>

      <div class="text-xs text-gray-500">
        <p>提示：输入裁剪框时请确保不超出视频原始分辨率。</p>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, onUnmounted, nextTick, computed } from 'vue'

const originalFile = ref(null)
const originalUrl = ref(null)
const originalVideo = ref(null)
const originalSize = ref(0)
const naturalResolution = ref('未知')

const cropX = ref(0)
const cropY = ref(0)
const cropW = ref(640)
const cropH = ref(360)

const isProcessing = ref(false)
const progress = ref(0)

let canvas = null
let canvasCtx = null
let drawRaf = null
let captureStream = null
let mediaRecorder = null
let recordedChunks = []
let audioCtx = null
let audioSource = null
let audioDest = null

const resultBlob = ref(null)
const resultUrl = ref(null)
const resultSize = ref(0)

function formatBytes(bytes) {
  if (!bytes) return '0 B'
  const units = ['B','KB','MB','GB']
  let i = 0
  let v = bytes
  while (v >= 1024 && i < units.length-1) { v/=1024; i++ }
  return v.toFixed(2) + ' ' + units[i]
}

function onFileChange(e) {
  stopCrop()
  const file = e.target.files && e.target.files[0]
  if (!file) return
  originalFile.value = file
  originalSize.value = file.size
  if (originalUrl.value) URL.revokeObjectURL(originalUrl.value)
  originalUrl.value = URL.createObjectURL(file)
  resultBlob.value = null
  resultUrl.value = null

  nextTick(() => {
    const vid = originalVideo.value
    if (!vid) return
    vid.onloadedmetadata = () => {
      naturalResolution.value = `${vid.videoWidth}×${vid.videoHeight}`
      // clamp defaults
      cropW.value = Math.min(cropW.value, vid.videoWidth)
      cropH.value = Math.min(cropH.value, vid.videoHeight)
    }
    vid.load()
  })
}

const readyToStart = computed(() => !!originalFile.value && !!originalUrl.value && !isProcessing.value)

async function startCrop() {
  if (!originalFile.value) { alert('请先选择视频文件'); return }
  if (!('MediaRecorder' in window) || !HTMLCanvasElement.prototype.captureStream) { alert('当前浏览器不支持必要的 API（MediaRecorder 或 canvas.captureStream）'); return }

  const vid = originalVideo.value
  if (!vid) return
  if (!vid.duration || !vid.videoWidth) await new Promise(res => { vid.onloadedmetadata = res; vid.load() })

  // clamp crop rect
  const sx = Math.max(0, Math.min(cropX.value, vid.videoWidth-1))
  const sy = Math.max(0, Math.min(cropY.value, vid.videoHeight-1))
  const sw = Math.max(1, Math.min(cropW.value, vid.videoWidth - sx))
  const sh = Math.max(1, Math.min(cropH.value, vid.videoHeight - sy))

  canvas = document.createElement('canvas')
  canvas.width = sw
  canvas.height = sh
  canvasCtx = canvas.getContext('2d')

  captureStream = canvas.captureStream(25)

  try {
    audioCtx = new (window.AudioContext || window.webkitAudioContext)()
    audioSource = audioCtx.createMediaElementSource(vid)
    audioDest = audioCtx.createMediaStreamDestination()
    audioSource.connect(audioDest)
    audioDest.stream.getAudioTracks().forEach(t => captureStream.addTrack(t))
  } catch (e) { console.warn('无法加入音频：', e) }

  let mimeType = ''
  if (MediaRecorder.isTypeSupported('video/webm;codecs=vp9')) mimeType = 'video/webm;codecs=vp9'
  else if (MediaRecorder.isTypeSupported('video/webm;codecs=vp8')) mimeType = 'video/webm;codecs=vp8'
  else mimeType = 'video/webm'

  try {
    recordedChunks = []
    mediaRecorder = new MediaRecorder(captureStream, { mimeType })
  } catch (err) { console.error('无法创建 MediaRecorder：', err); alert('无法创建 MediaRecorder（可能是不支持的编码或参数）'); return }

  mediaRecorder.ondataavailable = e => { if (e.data && e.data.size > 0) recordedChunks.push(e.data) }
  mediaRecorder.onstop = onRecorderStop

  function drawLoop() {
    try { canvasCtx.drawImage(vid, sx, sy, sw, sh, 0, 0, canvas.width, canvas.height) } catch(e) {}
    drawRaf = requestAnimationFrame(drawLoop)
  }

  mediaRecorder.start(250)
  isProcessing.value = true
  progress.value = 0

  vid.muted = true
  vid.currentTime = 0
  await vid.play()
  drawRaf = requestAnimationFrame(drawLoop)

  vid.ontimeupdate = () => { if (vid.duration) progress.value = Math.min(1, vid.currentTime/vid.duration) }
  vid.onended = () => { try { if (mediaRecorder && mediaRecorder.state !== 'inactive') mediaRecorder.stop() } catch(e){} }
}

function onRecorderStop() {
  const blob = new Blob(recordedChunks, { type: 'video/webm' })
  resultBlob.value = blob
  resultSize.value = blob.size
  if (resultUrl.value) URL.revokeObjectURL(resultUrl.value)
  resultUrl.value = URL.createObjectURL(blob)
  cleanup()
  isProcessing.value = false
  progress.value = 1
}

function stopCrop() {
  try { if (mediaRecorder && mediaRecorder.state !== 'inactive') mediaRecorder.stop() } catch(e) {}
  if (drawRaf) { cancelAnimationFrame(drawRaf); drawRaf = null }
  cleanup()
  if (originalVideo.value) { try { originalVideo.value.pause(); originalVideo.value.currentTime = 0 } catch(e){} }
  isProcessing.value = false
  progress.value = 0
}

function cleanup() {
  if (captureStream) { captureStream.getTracks().forEach(t => { try{ t.stop() }catch(e){} }); captureStream = null }
  if (canvas) { try { canvas.width = 0; canvas.height = 0 } catch(e){} canvas = null; canvasCtx = null }
  try { if (audioSource) { try { audioSource.disconnect() } catch(e){} audioSource = null } if (audioDest) audioDest = null; if (audioCtx) { audioCtx.close().catch(()=>{}); audioCtx = null } } catch(e) {}
  mediaRecorder = null
}

function downloadResult() {
  if (!resultBlob.value) return
  const a = document.createElement('a')
  a.href = resultUrl.value
  a.download = `crop_${new Date().toISOString().replace(/[:.]/g,'-')}.webm`
  document.body.appendChild(a)
  a.click()
  a.remove()
}

onUnmounted(() => { try { if (originalUrl.value) URL.revokeObjectURL(originalUrl.value) } catch(e){} try { if (resultUrl.value) URL.revokeObjectURL(resultUrl.value) } catch(e){} stopCrop() })
</script>

<style scoped>
/* 保持风格 */
</style>
